﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.DirectoryServices;
using System.IO;
using System.Linq;
using System.Management;
using System.Reflection;
using System.Security.Principal;
using System.ServiceModel;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.Win32;
using SPVisualDev;
using SPVisualDev.Services;
using System.Security.AccessControl;

namespace SPVisualDev.Services
{
    [ServiceBehaviorAttribute(IncludeExceptionDetailInFaults = true)]
    public class MainService : IMainService
    {
        private static string S_Install_Path;

        static MainService()
        {
            try
            {
                RegistryKey regkey = Registry.LocalMachine.OpenSubKey("Software\\SPVisualDev", false);
                S_Install_Path = regkey.GetValue("InstallPath").ToString();
            }
            catch
            {
                try
                {
                    RegistryKey regkey = Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\SPVisualDev", false);
                    S_Install_Path = regkey.GetValue("InstallPath").ToString();
                }
                catch (Exception ex)
                {
                    throw new FaultException(ex.Message);
                }
            }
        }

        #region IFeatureService Members

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string GetServiceVersion()
        {
            try
            {
                Assembly asm = Assembly.GetExecutingAssembly();
                string version = asm.GetName().Version.ToString();

                return version;
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        private void CheckIfFarmAdminPerm()
        {
            if (SPFarm.Local == null)
                throw new Exception("SharePoint farm could not be found. Check the service log on account and make sure it has access to the SharePoint config db.");

            if (!SPFarm.Local.CurrentUserIsAdministrator())
                throw new UnauthorizedAccessException("Current user is not farm administrator.");
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public bool TestConnectivity()
        {
            try
            {
                CheckIfFarmAdminPerm();

                return true;

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void InstallFeature(string FeatureName, bool Force)
        {
            try
            {
                CheckIfFarmAdminPerm();

                using (AppDomainCreator<Worker> creator = new AppDomainCreator<Worker>())
                {
                    bool bSuccess = creator.Worker.InstallFeature(FeatureName, Force);
                    if (!bSuccess)
                        throw new Exception(creator.Worker.LastErrorMsg);
                }

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void UninstallFeature(string FeatureName, bool Force)
        {
            try
            {
                CheckIfFarmAdminPerm();

                using (AppDomainCreator<Worker> creator = new AppDomainCreator<Worker>())
                {
                    bool bSuccess = creator.Worker.UninstallFeature(FeatureName, Force);
                    if (!bSuccess)
                        throw new Exception(creator.Worker.LastErrorMsg);
                }

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void DeployWSP(string WSPFileName, string TempWSPFileName, string[] WebApplicationURLs, bool bGAC)
        {
            try
            {
                string tempPath = Path.GetTempPath();
                string tempFilePath = tempPath.EndsWith("\\") ? tempPath + TempWSPFileName : tempPath + "\\" + TempWSPFileName;
                string wspPath = tempPath.EndsWith("\\") ? tempPath + WSPFileName : tempPath + "\\" + WSPFileName;

                try
                {
                    CheckIfFarmAdminPerm();

                    if (File.Exists(wspPath))
                        File.Delete(wspPath);

                    File.Copy(tempFilePath, wspPath);

                    FileInfo fileInfo = new FileInfo(wspPath);
                    FileSecurity fileSecurity = fileInfo.GetAccessControl();
                    fileSecurity.SetAccessRuleProtection(false, false);
                    fileInfo.SetAccessControl(fileSecurity);


                    using (AppDomainCreator<Worker> creator = new AppDomainCreator<Worker>())
                    {
                        bool bSuccess = creator.Worker.DeployWSP(wspPath, WebApplicationURLs, bGAC);
                        if (!bSuccess)
                            throw new Exception(creator.Worker.LastErrorMsg);
                    }
                }
                finally
                {
                    if (File.Exists(wspPath))
                        File.Delete(wspPath);
                }

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void RecycleIISAppPools(string Url)
        {
            try
            {
                CheckIfFarmAdminPerm();

                IIS.RecycleIISAppPool(Url);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public int GetServicePID()
        {
            try
            {
                CheckIfFarmAdminPerm();

                return System.Diagnostics.Process.GetCurrentProcess().Id;
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public int GetWebApplicationPID(string Url)
        {
            try
            {
                CheckIfFarmAdminPerm();

                SPWebApplication webapp = SPWebApplication.Lookup(new Uri(Url));
                if (webapp == null)
                    throw new Exception("'" + Url +  "' is not a valid web application.");

                using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("select * from Win32_Process where Name='w3wp.exe'"))
                {
                    searcher.Scope = new ManagementScope(@"\\.\root\cimv2");

                    ManagementObjectCollection coll = searcher.Get();

                    if (coll.Count == 0)
                        return -1; //throw new Exception("No worker process found.");

                    foreach (ManagementObject obj in coll)
                    {
                        try
                        {
                            string cmdLine = obj.Properties["CommandLine"].Value as string;

                            if (string.IsNullOrEmpty(cmdLine))
                                throw new Exception("No command line for worker process found.");

                            //Parse name
                            try
                            {
                                //command line example: "c:\\windows\\system32\\inetsrv\\w3wp.exe -a \\\\.\\pipe\\iisipm68dfe47b-41d4-4cfb-9ea3-2fc9dc4c2169 -ap \"SharePoint Central Administration v3\""

                                int start = cmdLine.IndexOf("-ap") + 5;
                                int end = cmdLine.IndexOf("\"", start);

                                string appPoolNameFromCmdLine = cmdLine.Substring(start, end - start);

                                if (appPoolNameFromCmdLine.Equals(webapp.ApplicationPool.Name, StringComparison.OrdinalIgnoreCase))
                                {
                                    UInt32 pid = (UInt32)obj.Properties["ProcessId"].Value;

                                    return Convert.ToInt32(pid);
                                }
                            }
                            catch (Exception ex)
                            {
                                throw new Exception("Error parsing command line for worker process. " + ex.ToString());
                            }
                        }
                        finally
                        {
                            obj.Dispose();
                        }
                    }

                }


                return -1;
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void BinInstall(string BinFileName, string TempBinFileName, string ParentAssemblyFullName, bool Gac, int Operation, string[] AppBinURLs)
        {
            try
            {

                string tempPath = Path.GetTempPath();
                string tempAssemblyFilePath = Path.GetTempPath() + "\\" + TempBinFileName;

                try
                {
                    CheckIfFarmAdminPerm();

                    string binFilePath = tempPath.EndsWith("\\") ? tempPath + BinFileName : tempPath + "\\" + BinFileName;

                    if (Operation == 0)
                    {
                        if (!File.Exists(tempAssemblyFilePath))
                            throw new Exception("Binary file could not be found in temp path.");

                        if (File.Exists(binFilePath))
                            File.Delete(binFilePath);

                        File.Move(tempAssemblyFilePath, binFilePath);
                    }

                    bool isPDB = BinFileName.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase);

                    if (Gac)
                    {
                        if (Operation == 0)
                        {
                            if (isPDB)
                            {
                                string gacPath = null;

                                using (AppDomainCreator<Worker> creator = new AppDomainCreator<Worker>())
                                {
                                    gacPath = creator.Worker.GetGacPathForAssembly(ParentAssemblyFullName);
                                    if (gacPath == null)
                                        throw new Exception(creator.Worker.LastErrorMsg);

                                    gacPath = gacPath.Replace("file:\\", "");
                                }

                                File.Move(binFilePath, gacPath + "\\" + BinFileName);

                                FileInfo fileInfo = new FileInfo(gacPath + "\\" + BinFileName);
                                FileSecurity fileSecurity = fileInfo.GetAccessControl();
                                fileSecurity.SetAccessRuleProtection(false, false);
                                fileInfo.SetAccessControl(fileSecurity);
                            }
                            else
                            {
                                try
                                {
                                    FusionInstall.AddAssemblyToCache(binFilePath);
                                }
                                finally
                                {
                                    if (File.Exists(binFilePath))
                                        File.Delete(binFilePath);
                                }
                            }
                        }
                        else
                        {
                            FusionInstall.RemoveAssemblyFromCache(ParentAssemblyFullName.Replace(" ", ""));
                        }

                        return;
                    }
                    else
                    {
                        foreach (string webAppUrl in AppBinURLs)
                        {
                            try
                            {
                                SPWebApplication webapp = SPWebApplication.Lookup(new Uri(webAppUrl));

                                string asmFileName = Path.GetFileName(binFilePath);
                                string appBinPath = webapp.IisSettings[SPUrlZone.Default].Path.FullName + "\\bin\\" + asmFileName;

                                if (Operation == 0)
                                {
                                    if (!Directory.Exists(Path.GetDirectoryName(appBinPath)))
                                    {
                                        Directory.CreateDirectory(Path.GetDirectoryName(appBinPath));

                                        DirectoryInfo dirInfo = new DirectoryInfo(appBinPath);
                                        DirectorySecurity dirSecurity = dirInfo.GetAccessControl();
                                        dirSecurity.SetAccessRuleProtection(false, false);
                                        dirInfo.SetAccessControl(dirSecurity);
                                    }

                                    File.Copy(binFilePath, appBinPath, true);

                                    FileInfo fileInfo = new FileInfo(appBinPath);
                                    FileSecurity fileSecurity = fileInfo.GetAccessControl();
                                    fileSecurity.SetAccessRuleProtection(false, false);
                                    fileInfo.SetAccessControl(fileSecurity);

                                }
                                else
                                {
                                    File.Delete(appBinPath);
                                }
                            }
                            catch (Exception ex)
                            {
                                throw new Exception("Failed to copy assembly file to app bin. " + ex.Message);
                            }
                        }
                    }
                }
                finally
                {
                    try
                    {
                        if (File.Exists(tempAssemblyFilePath))
                            File.Delete(tempAssemblyFilePath);
                    }
                    catch { }
                }

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void ActivateFeature(string FeatureName, string Url, bool Force)
        {
            try
            {
                CheckIfFarmAdminPerm();

                using (AppDomainCreator<Worker> creator = new AppDomainCreator<Worker>())
                {
                    bool bSuccess = creator.Worker.ActivateFeature(FeatureName, Url, Force);
                    if (!bSuccess)
                        throw new Exception(creator.Worker.LastErrorMsg);
                }

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void DeactivateFeature(string FeatureName, string Url, bool Force)
        {
            try
            {
                CheckIfFarmAdminPerm();

                using (AppDomainCreator<Worker> creator = new AppDomainCreator<Worker>())
                {
                    bool bSuccess = creator.Worker.DeactivateFeature(FeatureName, Url, Force);
                    if (!bSuccess)
                        throw new Exception(creator.Worker.LastErrorMsg);
                }

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void PutFile(byte[] File, string DestinationPath)
        {
            try
            {
                CheckIfFarmAdminPerm();

                Features.PutFile(File, DestinationPath);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string GetContentTypeGroups(string Url)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return ContentTypes.GetContentTypeGroups(Url);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string GetContentTypesByGroup(string Url, string Group)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return ContentTypes.GetContentTypesByGroup(Url, Group);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string GetContentTypesData(string Url, string Name)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return ContentTypes.GetContentTypesData(Url, Name);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string GetAllBuiltInContentTypes()
        {
            try
            {
                CheckIfFarmAdminPerm();

                return ContentTypes.GetAllBuiltInContentTypes();

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public List<IExplorerObjectContainer> GetChildren(IExplorerObjectContainer Parent, string SubGroup)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return Navigation.GetChildren(Parent, SubGroup);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public List<string> GetSubGroups(IExplorerObjectContainer Parent)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return Navigation.GetSubGroups(Parent);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public Dictionary<string, string> GetProperties(IExplorerObjectContainer Parent)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return (Dictionary<string, string>)Navigation.GetProperties(Parent, false);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }



        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void DeletePathIn12Hive(string ProjectRelativePath, int PathKind)
        {
            try
            {
                CheckIfFarmAdminPerm();

                PushDown.DeletePathIn12Hive(ProjectRelativePath, PathKind);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public void CreateDirectoryIn12Hive(string ProjectRelativePath)
        {
            try
            {
                CheckIfFarmAdminPerm();

                PushDown.CreatePathIn12Hive(ProjectRelativePath);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string UploadTempFile(Stream FileToUpload)
        {
            try
            {
                string tempFilePath = Path.GetTempFileName();

                WriteStreamToFile(FileToUpload, tempFilePath);

                return Path.GetFileName(tempFilePath);
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
            finally
            {
                try
                {
                    FileToUpload.Close();
                }
                catch { }
            }
        }

        //TimeInfo: In UTC [0] = Created, [1] = Modified, [2], Last accessed
        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public bool PutFileIn12Hive(string TempFileName, DateTime[] TimeInfo, string ProjectRelativePath, bool OverWriteIfOlder, bool ForceOverwrite)
        {
            //System.Diagnostics.Debugger.Break();

            try
            {
                string tempPath = Path.GetTempPath();
                string tempFilePath = tempPath.EndsWith("\\") ? tempPath + TempFileName : tempPath + "\\" + TempFileName;

                try
                {
                    CheckIfFarmAdminPerm();

                    string path = PushDown.GetProjectItem12HivePath(ProjectRelativePath);
                    if (File.Exists(path) && !ForceOverwrite)
                    {
                        DateTime dest_last_modified = File.GetLastWriteTimeUtc(path);

                        if (TimeInfo[1] < dest_last_modified && !OverWriteIfOlder)
                        {
                            return false;
                        }
                    }

                    if (File.Exists(path))
                        File.Delete(path);

                    File.Copy(tempFilePath, path);

                    
                    FileInfo fileInfo = new FileInfo(path);
                    FileSecurity fileSecurity = fileInfo.GetAccessControl();
                    fileSecurity.SetAccessRuleProtection(false, false);
                    fileInfo.SetAccessControl(fileSecurity);
                    

                    File.SetCreationTimeUtc(path, TimeInfo[0]);
                    File.SetLastWriteTimeUtc(path, TimeInfo[1]);
                    File.SetLastAccessTimeUtc(path, TimeInfo[2]);

                    return true;
                }
                finally
                {
                    if (File.Exists(tempFilePath))
                        File.Delete(tempFilePath);
                }
            }
            catch (Exception ex)
            {
                throw new FaultException<Exception>(ex, ex.Message);
            }
        }


        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public bool FileExistsIn12Hive(string ProjectRelativePath, int PathKind)
        {
            try
            {
                CheckIfFarmAdminPerm();

                return PushDown.PathExistsIn12Hive(ProjectRelativePath, PathKind);

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public Stream GetFileIn12Hive(string _12HiveRelativePath)
        {
            try
            {
                CheckIfFarmAdminPerm();

                string abs_path = SharePointLibrary.GetSharePointInstallPath() + "\\" + _12HiveRelativePath;
                if (!File.Exists(abs_path))
                    throw new FileNotFoundException("Could not find file '" + _12HiveRelativePath + "' in 12 hive.");

                return new FileStream(abs_path, FileMode.Open, FileAccess.Read);
            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        [OperationBehavior(Impersonation = ImpersonationOption.Required)]
        public string[] EnumerateFilesIn12HiveDirectory(string _12HiveRelativePath)
        {
            try
            {
                CheckIfFarmAdminPerm();

                string abs_path = SharePointLibrary.GetSharePointInstallPath() + "\\" + _12HiveRelativePath;
                if (!Directory.Exists(abs_path))
                    throw new FileNotFoundException("Could not find directory '" + _12HiveRelativePath + "' in 12 hive.");

                List<string> list = new List<string>();

                foreach (string file in Directory.GetFiles(abs_path))
                {
                    list.Add(Path.GetFileName(file));
                }

                return list.ToArray();

            }
            catch (Exception ex)
            {
                throw new FaultException(ex.Message);
            }
        }

        #endregion


        private static int ExecuteCommand(string ExecutablePath, string arguments, out string output)
        {
            using (System.Diagnostics.Process proc = new Process())
            {
                proc.StartInfo.Arguments = arguments;
                proc.StartInfo.FileName = ExecutablePath;

                proc.StartInfo.UseShellExecute = false;
                proc.StartInfo.RedirectStandardOutput = true;
                proc.StartInfo.RedirectStandardError = true;
                proc.StartInfo.CreateNoWindow = true;

                proc.Start();

                output = proc.StandardOutput.ReadToEnd();
                if (proc.ExitCode != 0)
                {
                    if (output == null)
                        output = "Exitcode: " + proc.ExitCode.ToString();

                    throw new Exception("Error occured: " + output);
                }

                return proc.ExitCode;
            }
        }

        private static void WriteStreamToFile(Stream SourceStream, string DestinationFile)
        {
            int bufferSize = 32768;
            using (FileStream fs = new FileStream(DestinationFile, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize))
            {
                byte[] buffer = new byte[bufferSize];
                int c = 0;
                while ((c = SourceStream.Read(buffer, 0, bufferSize)) > 0)
                {
                    fs.Write(buffer, 0, c);
                }            
            }
        }

    }
}
